package com.jingdianjichi.subject.domain.service.impl;

import com.alibaba.fastjson.JSON;
import com.jingdianjichi.subject.common.entity.PageResult;
import com.jingdianjichi.subject.common.enums.IsDeleteFlagEnum;
import com.jingdianjichi.subject.common.util.IdWorkerUtil;
import com.jingdianjichi.subject.common.util.LoginUtil;
import com.jingdianjichi.subject.domain.convert.SubjectInfoConvert;
import com.jingdianjichi.subject.domain.entity.SubjectInfoBO;
import com.jingdianjichi.subject.domain.entity.SubjectOptionBO;
import com.jingdianjichi.subject.domain.hadler.subject.SubjectTypeHandler;
import com.jingdianjichi.subject.domain.hadler.subject.SubjectTypeHandlerFactory;
import com.jingdianjichi.subject.domain.redis.RedisUtil;
import com.jingdianjichi.subject.domain.service.SubjectInfoDomainService;
import com.jingdianjichi.subject.domain.service.SubjectLikedDomainService;
import com.jingdianjichi.subject.infra.basic.entity.SubjectInfo;
import com.jingdianjichi.subject.infra.basic.entity.SubjectInfoEs;
import com.jingdianjichi.subject.infra.basic.entity.SubjectLabel;
import com.jingdianjichi.subject.infra.basic.entity.SubjectMapping;
import com.jingdianjichi.subject.infra.basic.service.SubjectEsService;
import com.jingdianjichi.subject.infra.basic.service.SubjectInfoService;
import com.jingdianjichi.subject.infra.basic.service.SubjectLabelService;
import com.jingdianjichi.subject.infra.basic.service.SubjectMappingService;
import com.jingdianjichi.subject.infra.entity.UserInfo;
import com.jingdianjichi.subject.infra.rpc.UserRPC;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 题目信息领域服务实现
 * 负责实现题目信息的业务逻辑，包括题目的添加等操作。
 *
 * @author: WuYimin
 * Date: 2024-02-05
  */
@Service
@Slf4j // Lombok提供的日志框架注解，用于记录日志
public class SubjectInfoDomainServiceImpl implements SubjectInfoDomainService {

	@Resource
	private SubjectInfoService subjectInfoService; // 注入题目信息服务，用于操作题目信息的数据库操作

	@Resource
	private SubjectMappingService subjectMappingService; // 注入题目映射服务，用于操作题目分类和标签的映射关系的数据库操作

	@Resource
	private SubjectTypeHandlerFactory subjectTypeHandlerFactory; // 注入题目类型处理器工厂，用于获取对应题目类型的处理器

	@Resource
	private SubjectLabelService subjectLabelService;

	@Resource
	private SubjectEsService subjectEsService;

	@Resource
	private SubjectLikedDomainService subjectLikedDomainService;

	@Resource
	private UserRPC userRPC;

	@Resource
	private RedisUtil redisUtil;

	private static final String RANK_KEY = "subject_rank";


	/**
	 * 添加题目信息
	 *
	 * @param subjectInfoBO 题目信息业务对象，包含了需要添加的题目的所有信息
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void add(SubjectInfoBO subjectInfoBO) {
		// 记录日志
		if (log.isInfoEnabled()) {
			log.info("SubjectInfoDomainServiceImpl.add.bo:{}", JSON.toJSONString(subjectInfoBO));
		}

		// 将题目信息业务对象转换为数据库实体对象
		SubjectInfo subjectInfo = SubjectInfoConvert.INSTANCE.convertBOToInfo(subjectInfoBO);
		subjectInfo.setIsDeleted(IsDeleteFlagEnum.UN_DELETED.getCode());  // 设置题目存在
		// 插入题目信息到数据库
		subjectInfoService.insert(subjectInfo);
		// mybatis在执行插入后，使用<selectKey>来获取并返回插入记录的自增主键值，这个值会被赋给Java对象的id属性
		subjectInfoBO.setId(subjectInfo.getId());


		// 使用工厂加策略的模式，一个工厂 包含了 四种类型， 根据传入的type自动映射选择处理
		// 通过工厂获取对应题目类型的处理器
		SubjectTypeHandler handler = subjectTypeHandlerFactory.getHandler(subjectInfo.getSubjectType());

		// 使用处理器添加题目具体信息（如单选、多选等）
		handler.add(subjectInfoBO);

		// 准备题目分类和标签的映射关系列表
		List<Integer> categoryIds = subjectInfoBO.getCategoryIds();
		List<Integer> labelIds = subjectInfoBO.getLabelIds();
		List<SubjectMapping> mappingList = new LinkedList<>();

		// 构造题目分类和标签的映射关系实体并添加到列表中
		categoryIds.forEach(categoryId -> {
			labelIds.forEach(labelId -> {
				SubjectMapping subjectMapping = new SubjectMapping();
				subjectMapping.setSubjectId(subjectInfo.getId()); // 设置题目ID
				subjectMapping.setCategoryId(Long.valueOf(categoryId)); // 设置分类ID
				subjectMapping.setLabelId(Long.valueOf(labelId)); // 设置标签ID
				subjectMapping.setIsDeleted(IsDeleteFlagEnum.UN_DELETED.getCode());
				mappingList.add(subjectMapping);
			});
		});
		// 批量插入题目分类和标签的映射关系到数据库
		subjectMappingService.batchInsert(mappingList);

		// 将题目信息同步到Elasticsearch中，以支持全文搜索等功能
		SubjectInfoEs subjectInfoEs = new SubjectInfoEs();
		subjectInfoEs.setDocId(new IdWorkerUtil(1, 1, 1).nextId()); // 生成文档ID
		subjectInfoEs.setSubjectId(subjectInfo.getId()); // 设置题目ID
		subjectInfoEs.setSubjectAnswer(subjectInfoBO.getSubjectAnswer()); // 设置题目答案
		subjectInfoEs.setCreateTime(new Date().getTime()); // 设置创建时间
		subjectInfoEs.setCreateUser("书生"); // 设置创建者
		subjectInfoEs.setSubjectName(subjectInfo.getSubjectName()); // 设置题目名称
		subjectInfoEs.setSubjectType(subjectInfo.getSubjectType()); // 设置题目类型
		subjectEsService.insert(subjectInfoEs); // 插入到Elasticsearch
		// redis 放入zadd计入排行榜 （每来一个人出一道题，就会往key里面放入一个loginId，同时把数量+1）
		redisUtil.addScore(RANK_KEY, LoginUtil.getLoginId(),1);
	}




	/**
	 * 分页查询题目信息
	 * 根据业务对象提供的查询条件和分页参数，查询符合条件的题目信息。
	 *
	 * @param subjectInfoBO 查询条件封装在业务对象中
	 * @return 分页查询结果
	 */
	@Override
	public PageResult<SubjectInfoBO> getSubjectPage(SubjectInfoBO subjectInfoBO) {
		PageResult<SubjectInfoBO> pageResult = new PageResult<>();
		// 设置返回结果的当前页码为请求中的页码
		pageResult.setPageNo(subjectInfoBO.getPageNo());
		// 设置返回结果的每页数据量为请求中的数据量
		pageResult.setPageSize(subjectInfoBO.getPageSize());

		// 计算查询的起始位置，用于分页查询
		int start = (subjectInfoBO.getPageNo() - 1) * subjectInfoBO.getPageSize();
		// 将业务对象转换为数据库实体对象，准备进行查询
		SubjectInfo subjectInfo = SubjectInfoConvert.INSTANCE.convertBOToInfo(subjectInfoBO);
		// 根据条件查询符合条件的题目总数，用于分页计算总页数
		// subjectInfo 里面前端可以传 subject_difficult（题目难度），subject_type（题目类型 1单选 2多选 3判断 4简答）
		// 如果前端提供的参数符合 题目关系映射表（subject_mapping）中的 分类id和标签id 还有 映射表中的 subject_id（题目id）和 题目详情表中的id相等
		// 那么我先返回能能不能查到数据，如果能查到，再继续查每条数据的详细信息
		int count = subjectInfoService.countByCondition(subjectInfo, Math.toIntExact(subjectInfoBO.getCategoryId()),
				subjectInfoBO.getLabelId());
		// 如果没有找到任何记录，直接返回空的分页结果
		if (count == 0) {
			return pageResult; // 返回空结果
		}
		// 根据条件和计算出的起始位置进行 分页查询，获取当前页的数据列表
		List<SubjectInfo> subjectInfoList = subjectInfoService.queryPage(subjectInfo, Math.toIntExact(subjectInfoBO.getCategoryId()),
				subjectInfoBO.getLabelId(), start, subjectInfoBO.getPageSize());
		// 将查询到的数据库实体列表转换为业务对象列表，准备返回
		List<SubjectInfoBO> subjectInfoBOS = SubjectInfoConvert.INSTANCE.convertListInfoToBo(subjectInfoList);
		// 设置返回结果的记录列表为转换后的业务对象列表
		pageResult.setRecords(subjectInfoBOS);
		// 设置返回结果的总记录数
		pageResult.setTotal(count);
		return pageResult; // 返回构建好的分页查询结果
	}



	/**
	 * 查询题目详情
	 * 根据题目ID查询题目的详细信息，包括题目的选项等。
	 *
	 * @param subjectInfoBO 包含查询条件的业务对象
	 * @return 题目详细信息业务对象
	 */
	@Override
	public SubjectInfoBO querySubjectInfo(SubjectInfoBO subjectInfoBO) {
		// 根据题目ID查询数据库，获取题目的基本信息
		SubjectInfo subjectInfo = subjectInfoService.queryById(subjectInfoBO.getId());

		// 通过题目类型处理器工厂获取对应题目类型的处理器
		SubjectTypeHandler handler = subjectTypeHandlerFactory.getHandler(subjectInfo.getSubjectType());
		// 使用获取到的处理器查询题目的选项等详细信息
		SubjectOptionBO optionBo = handler.query(Math.toIntExact(subjectInfo.getId()));
		// 将查询到的题目基本信息和详细信息合并，转换成业务对象用于返回
		SubjectInfoBO bo = SubjectInfoConvert.INSTANCE.convertOptionAndInfoToBo(optionBo, subjectInfo);
		// 创建题目映射查询对象，准备查询题目关联的标签ID列表
		SubjectMapping subjectMapping = new SubjectMapping();
		subjectMapping.setSubjectId(subjectInfo.getId()); // 设置查询条件：题目ID
		subjectMapping.setIsDeleted(IsDeleteFlagEnum.UN_DELETED.getCode()); // 设置查询条件：未删除的标签
		// 查询题目关联的所有标签ID
		List<SubjectMapping> mappingList = subjectMappingService.queryLabelId(subjectMapping);
		// 从映射关系列表中提取所有标签ID
		List<Long> labelIdList = mappingList.stream().map(SubjectMapping::getLabelId).collect(Collectors.toList());
		// 根据标签ID列表批量查询标签信息
		List<SubjectLabel> labelList = subjectLabelService.batchQueryById(labelIdList);
		// 从标签信息列表中提取所有标签名称
		List<String> labelNameList = labelList.stream().map(SubjectLabel::getLabelName).collect(Collectors.toList());
		// 将查询到的标签名称列表设置到返回的业务对象中
		bo.setLabelName(labelNameList);
		// 设置该题目是否点赞
		bo.setLiked(subjectLikedDomainService.isLiked(subjectInfoBO.getId().toString(), LoginUtil.getLoginId()));
		// 设置该题目的点赞数量
		bo.setLikedCount(subjectLikedDomainService.getLikedCount(subjectInfoBO.getId().toString()));
		// 处理题目的前后导航逻辑
		assembleSubjectCursor(subjectInfoBO, bo);
		return bo; // 返回包含题目详细信息的业务对象
	}

	// 处理题目的前后导航逻辑的方法
	private void assembleSubjectCursor(SubjectInfoBO subjectInfoBO, SubjectInfoBO bo) {
		Long categoryId = subjectInfoBO.getCategoryId(); // 获取题目所属的分类ID
		Long labelId = subjectInfoBO.getLabelId(); // 获取题目所关联的标签ID
		Long subjectId = subjectInfoBO.getId(); // 获取当前题目的ID
		// 如果分类ID或标签ID为空，则不处理前后导航逻辑
		if (Objects.isNull(categoryId) || Objects.isNull(labelId)) {
			return;
		}
		// 查询下一题的ID
		Long nextSubjectId = subjectInfoService.querySubjectIdCursor(subjectId, categoryId, labelId, 1);
		bo.setNextSubjectId(nextSubjectId); // 设置下一题的ID
		// 查询上一题的ID
		Long lastSubjectId = subjectInfoService.querySubjectIdCursor(subjectId, categoryId, labelId, 0);
		bo.setLastSubjectId(lastSubjectId); // 设置上一题的ID
	}

	/**
	 * 全文检索
	 * @param subjectInfoBO 题目信息业务对象，包含了分页参数和搜索关键字
	 * @return 分页查询结果，包含了符合条件的题目信息列表和分页详情
	 */
	@Override
	public PageResult<SubjectInfoEs> getSubjectPageBySearch(SubjectInfoBO subjectInfoBO) {
		// 创建题目信息ES实体，并设置查询的分页参数和关键字
		SubjectInfoEs subjectInfoEs = new SubjectInfoEs();
		subjectInfoEs.setPageNo(subjectInfoBO.getPageNo()); // 设置请求的页码
		subjectInfoEs.setPageSize(subjectInfoBO.getPageSize()); // 设置每页显示的数量
		subjectInfoEs.setKeyWord(subjectInfoBO.getKeyWord()); // 设置搜索关键字
		// 调用服务层执行查询操作，并返回查询结果
		return subjectEsService.querySubjectList(subjectInfoEs);
	}

	///**
	// * 获取题目贡献榜（数据库实现）
	// * 通过调用`subjectInfoService`的`getContributeCount`方法来获取每个用户贡献的题目数量的列表。
	// * @return
	// */
	//@Override
	//public List<SubjectInfoBO> getContributeList() {
	//	// 调用service层方法获取每个用户贡献的题目数量列表
	//	List<SubjectInfo> subjectInfoList = subjectInfoService.getContributeCount();
	//	// 如果查询结果为空，返回一个空列表
	//	if(CollectionUtils.isEmpty(subjectInfoList)) {
	//		return Collections.emptyList();
	//	}
	//	// 非空时处理查询结果
	//	List<SubjectInfoBO> boList = new LinkedList<>();
	//	subjectInfoList.forEach((subjectInfo -> {
	//		// 创建业务对象并设置题目数量
	//		SubjectInfoBO subjectInfoBO = new SubjectInfoBO();
	//		subjectInfoBO.setSubjectCount(subjectInfo.getSubjectCount());  // 设置用户贡献的题目数量
	//		// 调用远程服务获取用户信息
	//		UserInfo userInfo = userRPC.getUserInfo(subjectInfo.getCreatedBy());
	//		// 设置业务对象的创建者信息
	//		subjectInfoBO.setCreateUser(userInfo.getNickName());  // 设置用户昵称
	//		subjectInfoBO.setCreateUserAvatar(userInfo.getAvatar()); // 设置用户头像
	//		// 将业务对象添加到列表中
	//		boList.add(subjectInfoBO);
	//	}));
	//	// 返回业务对象列表
	//	return boList;
	//}


	/**
	 * 获取题目贡献榜（Redis缓存实现）。此方法从Redis缓存中获取题目贡献榜的数据。
	 *
	 * @return 返回题目贡献榜的业务对象列表，包含了题目贡献者的相关信息。
	 */
	@Override
	public List<SubjectInfoBO> getContributeList() {
		// 从Redis中获取指定键的有序集合中得分最高的前6个成员
		Set<ZSetOperations.TypedTuple<String>> typedTuples = redisUtil.rankWithScore(RANK_KEY, 0, 5);
		// 记录日志
		if (log.isInfoEnabled()) {
			log.info("getContributeList.typedTuples:{}", JSON.toJSONString(typedTuples));
		}
		// 如果获取的集合为空，则返回一个空的列表
		if (CollectionUtils.isEmpty(typedTuples)) {
			return Collections.emptyList();
		}
		// 创建一个列表，用于存储转换后的业务对象
		List<SubjectInfoBO> boList = new LinkedList<>();
		// 遍历有序集合中的成员
		typedTuples.forEach((rank -> {
			// 创建一个新的业务对象，并设置相关属性
			SubjectInfoBO subjectInfoBO = new SubjectInfoBO();
			subjectInfoBO.setSubjectCount(rank.getScore().intValue()); // 设置用户贡献的题目数量
			UserInfo userInfo = userRPC.getUserInfo(rank.getValue()); // 通过RPC调用获取用户信息
			subjectInfoBO.setCreateUser(userInfo.getNickName()); // 设置用户昵称
			subjectInfoBO.setCreateUserAvatar(userInfo.getAvatar()); // 设置用户头像
			// 将业务对象添加到列表中
			boList.add(subjectInfoBO);
		}));
		return boList; // 返回转换后的业务对象列表
	}


}
